package com.lx.base.registrsar;

import com.alibaba.fastjson.JSON;
import com.lx.base.aop.BizLogger;
import com.lx.base.exception.BaseException;
import com.lx.base.exception.SysException;
import com.google.common.base.Joiner;
import com.google.common.base.Stopwatch;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.bind.MethodArgumentNotValidException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;


@Aspect
@Slf4j
@Component
public class BizLoggerAspect implements Ordered {

    @Override
    public int getOrder() {
        return 10;
    }

    @Around("@annotation(com.lx.base.aop.BizLogger)")
    public Object aroundInvoke(ProceedingJoinPoint point) throws Throwable {

        return invoke(point);

    }

    protected Object invoke(ProceedingJoinPoint point) throws Throwable {
        Signature signature = point.getSignature();
        if (signature instanceof MethodSignature) {
            Method method = ((MethodSignature) signature).getMethod();
            Class targetClass = point.getTarget().getClass();

            BizLogger anno = AnnotationUtils.findAnnotation(method, BizLogger.class);
            if (Objects.isNull(anno)) {
                anno = AnnotationUtils.findAnnotation(targetClass, BizLogger.class);
            }

            Stopwatch sw = Stopwatch.createStarted();

            Object execResult = null;
            boolean failed = false;

            Throwable ex = null;
            try {
                execResult = point.proceed();
                return execResult;
            } catch (ConstraintViolationException | MethodArgumentNotValidException | BindException e) {
                failed = true;
                ex = e;
                throw e;
            } catch (Throwable e) {
                failed = true;
                ex = e;
                if (e instanceof BaseException) {
                    throw e;
                }
                throw new SysException(e, e.getMessage());
            } finally {
                if (sw.isRunning()) {
                    sw.stop();
                }
                try {
                    String targetClassName = targetClass.getSimpleName();
                    StringBuilder sb = new StringBuilder(512);
                    sb.append(" call method: ").append(targetClassName).append("#").append(method.getName());
                    sb.append(", ellapsed: ").append(sw.elapsed(TimeUnit.MILLISECONDS)).append(" ms");
                    if (!Strings.isNullOrEmpty(anno.desc())) {
                        sb.append(", desc: ").append(anno.desc());
                    }
                    if (anno.isInParam() || failed) {
                        sb.append(", params: [");
                        // 入参JSON序列化
                        Object[] args = point.getArgs();
                        if (!ObjectUtils.isEmpty(args)) {
                            List<String> params = Lists.newArrayList();
                            int len = args.length;
                            for (int i = 0; i < len; i++) {
                                Object obj = args[i];
                                if (Objects.nonNull(obj) && (obj instanceof Errors
                                        || obj instanceof HttpServletRequest
                                        || obj instanceof HttpServletResponse
                                        || obj instanceof Model
                                        || obj instanceof ModelMap)) {
                                    continue;
                                }
                                if (Objects.isNull(obj)) {
                                    params.add("null");
                                } else if (obj instanceof CharSequence) {
                                    params.add(obj.toString());
                                } else if (obj instanceof Number) {
                                    params.add(obj.toString());
                                } else if (obj instanceof java.io.Serializable) {
                                    params.add(JSON.toJSONString(obj));
                                } else if (Objects.nonNull(obj)) {
                                    params.add(obj.toString());
                                }
                            }
                            sb.append(Joiner.on(", ").join(params));
                        }
                        sb.append("]");
                    }

                    if (anno.isOutResult() || failed) {
                        sb.append(", result: ");
                        if (Objects.nonNull(execResult)) {
                            // 返回结果JSON序列化
                            if (execResult instanceof java.io.Serializable) {
                                sb.append(JSON.toJSONString(execResult));
                            } else {
                                // 包含了Void返回类型
                                sb.append(execResult.toString());
                            }
                        } else {
                            // 抛出异常或返回null时
                            if (!failed) {
                                sb.append("null");
                            } else {
                                sb.append("-");
                            }
                        }
                    }

                    // 抛异常的调用，打印error日志
                    if (failed) {
                        /*if (Objects.nonNull(ex) && (ex instanceof Exception)) {
                            log.error(ExceptionLog.create((Exception) ex , sb.toString()).toString());
                        }else {*/
                        log.error(sb.toString());
//                        }
                    } else {
                        log.info(sb.toString());
                    }
                } catch (Exception e) {
                    log.error("Method Access Log failed.", e);
                }
            }
        } else {
            // 其他情况不拦截
            return point.proceed();
        }
    }
}
